﻿using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Input;
using Bling.Util;
using Bling.Core;
using Bling.DSL;
using Bling.WPF;
using Bling.Graphics;
using Bling.DX;
using Bling.Matrices;
using Bling.WPF3D;
using Bling.Angles;
namespace Bling.Example {
  public class BigCube : HasDX10Canvas {
    public BigCube()
      // by passing true to base, an overlay window will be created.
      : base(true) {
      Host.Size = new Double2Bl(500, 500); // host size must be constant!
      Host.LeftTop = 0d;
      // background of overlay can't be transparent or it won't receive input. It can be a very transparent color though, go fig. 
      OverlayContent.Background = new SolidColorBrushBl() { Color = Colors.Red, Opacity = OverlayContent.IsMouseOver.Condition(.15d, .01d), };
      // create a virtual trackpad to define Rotate.
      QuaternionBl Rotate;
      {
        // An ellipse to represent our virtual trackpad, only visible when the mouse is over the overlay
        var sphere = new EllipseBl(OverlayContent) {
          CenterPosition = OverlayContent.CenterSize,
          Stroke = { Brush = Brushes.White, Thickness = 2d },
          Size = 400d,
          Visibility = OverlayContent.IsMouseOver,
          // so mouse events on the sphere are passed to overlay content instead
          IsHitTestVisible = false,
        };
        // rotation is an anonymous property that can undergo data binding.
        Rotate = this.Property(new QuaternionBl(1d.Bl().XXX(), .33.PI()));
        // compute virtual sphere mouse location according to mouse's 2D position
        Double2Bl MouseAt = sphere.Mouse.Position / sphere.Size;
        // screen cooridnates are [0,1] while 3D coordinates are [-1,1], use n * 2d - 1 to translate.
        MouseAt = MouseAt * 2d - 1d;
        // for some reason, X is flipped in result, easy enough to fix. 
        MouseAt = MouseAt * new Double2Bl(-1d, +1d);
        // determine Z value based on X and Y values, if x,y are outside of the sphere, simply use 0.
        Double3Bl MouseAt3D = new Double3Bl(MouseAt, (1d - MouseAt.LengthSquared).Max(0d).Sqrt);
        MouseAt3D = MouseAt3D.Normalize;

        // Rotation update occurs on mouse drag
        // prepend the angle between the current mouse and mouse at the beginning of the
        // dragging to the rotation at the beginning of the rotation. 
        Rotate[OverlayContent.Mouse.Drag.Update] =
          MouseAt3D.Normalize.AngleBetween(MouseAt3D[OverlayContent.Mouse.Drag.In]) *
          Rotate[OverlayContent.Mouse.Drag.In];

      }
      // A cube vertex topology, 4 vertices per face.
      CubeMesh Cube = new CubeMesh();

      //  create a perspective camera.
      Double3Bl WorldCenter = new Double3Bl(0, 0, 0);
      // camera position is used in lighting, so externalize
      Double3Bl CameraPosition = WorldCenter + new Double3Bl(0, 0, -4);
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = CameraPosition,
      };
      Device.Render(Cube, (CubeMesh.Mesh3DEntry Entry, IVertex vertex) => {
        // n is the vertex index, there are four vertices per face, so divide by 4 to find the face id.
        //var face = Entry.Index / 4;

        // quaternions for both x and y rotation, determined by sliders.
        // m is the instance index, there are 16 instances, arrange in a square configuration using %/ by 4.

        var UseMatrix = ((Camera.Project * Camera.View) * Rotate.Matrix * Matrix4Bl.MakeScale(.5)); // *RotateOther.Matrix;

        // find the xyz coordinate of the current vertex index
        var WorldPos = UseMatrix.Transform(Entry.Position);
        vertex.Position = WorldPos;


        var FaceColors = new ColorBl[] {
          Colors.White.Bl(),
          Colors.LightGreen.Bl(),
          Colors.Yellow.Bl(), // messed up
          Colors.LightPink.Bl(), // messed up
          Colors.LightSkyBlue.Bl(),
          Colors.Salmon.Bl(),
        };
        Tex2D clr = (Tex2D)
          FaceColors.Table(Entry.Face);
          //Colors.White.Bl(); // UseTexture; //  FaceColors.Table(Entry.Face % FaceColors.Length);
        // The normal comes from the cube topology, but must be transformed according to the current rotation.
        // xxyyxx
        // zzzzyy
        Double2Bl uv;
        {
          //var v0 = ((Entry.Face / 2 % 2) == 0).Condition(xyz.X, xyz.Y);
          //var v1 = (Entry.Face < 4).Condition((xyz.Z), xyz.Y);

          uv = Entry.Texture.PerPixel(); // new Double2Bl(v0, v1) + (Entry.Texture.PerPixel()) / (DIM);
        }
        Double3Bl normal = new Double3Bl(0, 0, -1);
        {
          normal = Entry.Normal;
          //var rotateA = Rotate;
          //var rotateC = Entry.Normal.AngleBetween(new Double3Bl(+0, +0, -1).Normalize);
          normal = Rotate.Matrix.Transform(normal).Normalize;
          var NormalTextures = new Texture2Bl[] {
            new ImageTexture<D2>("Resources/890-normal.jpg".MakePackUri(typeof(BigCube))),
            new ImageTexture<D2>("Resources/243-normal.jpg".MakePackUri(typeof(BigCube))),
            new ImageTexture<D2>("Resources/1146-normal.jpg".MakePackUri(typeof(BigCube))),
            new ImageTexture<D2>("Resources/4336-normal.jpg".MakePackUri(typeof(BigCube))),
            new ImageTexture<D2>("Resources/Stone-normal.jpg".MakePackUri(typeof(BigCube))),
          };
          //Texture2Bl NormalTexture = new ImageTexture<D2>("Resources/243-normal.jpg".MakePackUri(typeof(Advanced)));
          Texture2Bl NormalTexture = NormalTextures.Table(Entry.Face % NormalTextures.Length);
          {
            var norm = ((NormalTexture[uv].XYZ() * 2 - 1) * new Double3Bl(1, 1, -1)).Normalize;
            var angle = norm.AngleBetween(new Double3Bl(+0, +0, -1).Normalize);
            //normal = norm.Rotate(normal) * new Double3Bl(1,1,-1);


            normal = angle.Transform(normal);
          }
        }
        
        // create a slider to control light height.

        // illuminate the cubes with a point light on a diffuse material.
        DoubleBl d = 0d;  //this.Property(0d.Bl());
        //d.Animate().Forever().Duration(1000d).To = +1d;
        LightBl light = new SpotLightBl() {
          Direction = (new Double3Bl(0d, 0d, /*d.PI2.SinCos * .1d,*/ +1)).Normalize,
          Color = Colors.White.Bl() * (2d * d - 1d).Abs * .5,
          Position = new Double3Bl(0, 0, -10),
          InnerConeAngle = 2.Degrees(),
          OuterConeAngle = 4.Degrees(),
        };
        DirectionalLightBl dirLight = new DirectionalLightBl() {
          Direction = (new Double3Bl(-.5, 0, +1)).Normalize,
          Color = Colors.White.Bl() * .5,
          //Color = new ColorBl(1, 1, 1),
        };
        MaterialCl diffuse = new DiffuseMaterialCl() {
          // how much non-ambient light is relfected
          Knob = .8,
          Ambient = {
            // how much ambient light is reflected
            Knob = .2
          }
        };
        // combine light with diffuse material via apply, plugin in world position, normal, and camera position, then color. 
        // also, compose an ambient light so nothing is completely dark.

        //xy = 1d - xy;
        //xy = new Double2Bl(1d - xy.X, xy.Y);
        var clr0 = diffuse.
          ApplyLights(light + dirLight + new AmbientLightBl() { Color = Colors.White }).
          ApplyAt(WorldPos.PerPixel(), normal, CameraPosition).ForPixel(clr, uv);
        vertex.Color = clr0;
      });
    }
  }
}
